#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <mad.h>
#include <pthread.h>
#include "Mp3Decoder.hpp"

#define CACHE_BUF_LEN            (96 * 1024)
static inline int16_t scale(mad_fixed_t sample)
{
  /* round */
  sample += (1L << (MAD_F_FRACBITS - 16));

  /* clip */
  if (sample >= MAD_F_ONE)
    sample = MAD_F_ONE - 1;
  else if (sample < -MAD_F_ONE)
    sample = -MAD_F_ONE;

  /* quantize */
  return sample >> (MAD_F_FRACBITS + 1 - 16);
}


Mp3Decoder::Mp3Decoder(FRunLoop *runLoop, const char *path) :
    runLoop(runLoop),
    state(State::IDLE), filePath(strdup(path))
{
    pthread_mutex_init(&mutex, NULL);
    sem_init(&sem_produce, 0, 0);
}

void Mp3Decoder::createThread()
{
    pthread_create(&th, NULL, [](void *parm)->void* {
        Mp3Decoder *decoder = (Mp3Decoder *)parm;
        mad_decoder_run(&decoder->decoder, MAD_DECODER_MODE_SYNC);
        while (!decoder->interrupt) {
            if (decoder->state == State::WORKING) {
                decoder->state = State::FINISH;
                FEvent *event = new FEvent([decoder](FEvent *e) {
                    decoder->state = State::FINISH;
                    if (decoder->stateChangeCB) {
                        decoder->stateChangeCB(decoder);
                    }
                    decoder->finishEvent = nullptr;
                    delete e;
                });
                decoder->runLoop->addEvent(event);
                decoder->finishEvent = event;
            }
            sem_wait(&decoder->sem_produce);
        }
        return nullptr;
    }, (void *)this);
}

Mp3Decoder::State Mp3Decoder::getState()
{
    return state;
}

const char *Mp3Decoder::stateToString(State state)
{
    switch (state) {
        case State::IDLE: return "IDLE";
        case State::LOADING: return "LOADING";
        case State::WORKING: return "WORKING";
        case State::FINISH: return "FINISH";
    }
    return nullptr;
}

bool Mp3Decoder::start()
{
    F_ASSERT(!postedDestroy);
    if (state != State::IDLE) {
        printf("Mp3Decoder start but state:%d err\n", state);
        recentError = Error::STATE_ERR;
        return false;
    }
    int fd = open(filePath, O_RDONLY);
    if (fd < 0) {
        printf("open mp3 file err:%d\n", errno);
        recentError = Error::UNABLE_OPEN_FILEPATH;
        return false;
    }
    state = State::LOADING;
    fileFd = fd;
    pcmBuffer = (uint8_t *)malloc(CACHE_BUF_LEN);
    if (!pcmBuffer) {
        printf("mp3decoder unable malloc:%dKB\n", CACHE_BUF_LEN/1024);
        recentError = Error::NO_MEM;
        return false;
    }
    ring_buf_init(&cache, pcmBuffer, CACHE_BUF_LEN);

    mad_decoder_init(&decoder, this, [](void *data, struct mad_stream *stream) {
        size_t fill_size, leak_len;
        Mp3Decoder *decoder = (Mp3Decoder *)data;
        if (decoder->interrupt) return MAD_FLOW_STOP;

        fill_size = LIBMAD_INPUT_BUF_LEN;
        leak_len = stream->bufend - stream->next_frame;
        if (leak_len > 0) {
            memmove(decoder->libmad_input_buf, stream->next_frame, leak_len);
            fill_size -= leak_len;
        }

        if (fill_size > 0) {
            void *empty_addr = decoder->libmad_input_buf + leak_len;
            size_t read_size = read(decoder->fileFd, empty_addr, fill_size);
            if (read_size <= 0) {
                return MAD_FLOW_STOP;
            } else {
                leak_len += read_size;
            }
        }

        mad_stream_buffer(stream, decoder->libmad_input_buf, leak_len);

        return MAD_FLOW_CONTINUE;
    }, [](void *data, struct mad_header const *header) {
        enum mad_mode channel_mode = header->mode;
        uint8_t channel;
        Mp3Decoder *decoder = (Mp3Decoder *)data;
        if (decoder->interrupt)               return MAD_FLOW_STOP;
        if (decoder->state != State::LOADING) return MAD_FLOW_CONTINUE;
        if (decoder->notifyPCMInfoEvent)      return MAD_FLOW_CONTINUE;

        if (channel_mode == MAD_MODE_SINGLE_CHANNEL)
            channel = 1;
        else
            channel = 2;

        printf("heade samplerate:%d\n", header->samplerate);
        decoder->pcmInfo.channel = channel;
        decoder->pcmInfo.simplerate = header->samplerate;
        decoder->pcmInfo.bitrate = header->bitrate;

        FEvent *event = new FEvent([decoder](FEvent *e) {
            if (decoder->stateChangeCB) {
                decoder->stateChangeCB(decoder);
            }
            decoder->notifyPCMInfoEvent = nullptr;
            delete e;
        });
        decoder->state = State::WORKING;
        decoder->runLoop->addEvent(event);
        decoder->notifyPCMInfoEvent = event;

        return MAD_FLOW_CONTINUE;
    }, /*filter*/nullptr, [](void *data, struct mad_header const *header,
                             struct mad_pcm *pcm) {
        Mp3Decoder *decoder = (Mp3Decoder *)data;
        if (decoder->interrupt) return MAD_FLOW_STOP;

        const mad_fixed_t *left_ch, *right_ch;

        left_ch   = pcm->samples[0];
        right_ch  = pcm->samples[1];
    
        size_t len = pcm->length * 2 * pcm->channels;

        uint16_t *cache = (uint16_t *)malloc(len);
        uint16_t *cache_temp = cache;
        for (long i = 0; i < pcm->length; i++) {
            *(cache_temp++) = scale(left_ch[i]);
            if (pcm->channels == 2) {
                *(cache_temp++) = scale(right_ch[i]);
            }
        }

        if (len > CACHE_BUF_LEN) {
            printf("mp3 dec cache buf too short:0x%x\n", CACHE_BUF_LEN);
            free(cache);
            return MAD_FLOW_CONTINUE;
        }
        while (len > ring_buf_size_empt(&decoder->cache) &&
               !decoder->interrupt) {
            sem_wait(&decoder->sem_produce);
        }

        ring_buf_push(&decoder->cache, (uint8_t *)cache, len);
        
        if (decoder->pcmCacheEmpty && !decoder->havePCMEvent) {
            FEvent *event = new FEvent([decoder](FEvent *e) {
                decoder->state = State::WORKING;
                if (decoder->stateChangeCB) {
                    decoder->stateChangeCB(decoder);
                }
                printf("post pcm\n");
                decoder->havePCMEvent = nullptr;
                delete e;
            });
            decoder->runLoop->addEvent(event);
            decoder->havePCMEvent = event;
        }

        free(cache);
        return MAD_FLOW_CONTINUE;
    }, [](void *data, struct mad_stream *stream, struct mad_frame *frame) {
        Mp3Decoder *decoder = (Mp3Decoder *)data;
        if (decoder->interrupt) return MAD_FLOW_STOP;
        return MAD_FLOW_CONTINUE;
    }, nullptr/*message*/);

    createThread();

    return true;
}

void Mp3Decoder::seekTo(int ms)
{
    F_ASSERT(!postedDestroy);
    if (state <= State::LOADING) {
        printf("Mp3Decoder start but state:%d err\n", state);
        return;
    }
    interrupt = true;
    sem_post(&sem_produce);

    void *ret;
    pthread_join(th, &ret);
    if (havePCMEvent) {
        delete havePCMEvent;
        havePCMEvent = nullptr;
    }
    if (finishEvent) {
        delete finishEvent;
        finishEvent = nullptr;
    }

    interrupt = false;
    lseek(fileFd, ms * pcmInfo.bitrate/1000, SEEK_SET);
    createThread();
}

const AudioPCMInfo *Mp3Decoder::getPCMInfo()
{
    F_ASSERT(!postedDestroy);
    if (state <= State::LOADING) {
        printf("Mp3Decoder start but state:%d err\n", state);
        return nullptr;
    }
    return &pcmInfo;
}

int Mp3Decoder::readPCM(int16_t *pcm, int len)
{
    int rev;
    F_ASSERT(!postedDestroy);
    if (state <= State::LOADING) {
        printf("Mp3Decoder start but state:%d err\n", state);
        return -1;
    }

    pcmCacheEmpty = ring_buf_size_data(&cache) == 0;
    rev = ring_buf_shift(&cache, (uint8_t *)pcm, len * 2);
    sem_post(&sem_produce);

    if (pcmCacheEmpty)
    printf("enable empty flag\n");

    return rev / 2;
}

Mp3Decoder::~Mp3Decoder()
{
    if (state >= State::LOADING) {
        void *ret;
        pthread_join(th, &ret);

        mad_decoder_finish(&decoder);

        free(pcmBuffer);
        ring_buf_deinit(&cache);

        close(fileFd);

        if (notifyPCMInfoEvent) {
            delete notifyPCMInfoEvent;
        }
        if (havePCMEvent) {
            delete havePCMEvent;
        }
        if (finishEvent) {
            delete finishEvent;
        }
    }
    pthread_mutex_destroy(&mutex);
    sem_destroy(&sem_produce);
    free(filePath);
}

void Mp3Decoder::destroy()
{
    F_ASSERT(!postedDestroy);
    FEvent *event = new FEvent([](FEvent *e) {
        delete e;
    });
    runLoop->addEvent(event);
    finishEvent = event;
    postedDestroy = true;

    interrupt = true;
    sem_post(&sem_produce);
}

void Mp3Decoder::printCache()
{
    int empty = ring_buf_size_empt(&cache);
    int data  = ring_buf_size_data(&cache);
    printf("Cache empty:%d data:%d\n", empty, data);
}
